package com.autumn.platform.core.boot;

import java.security.SecureRandom;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.ClassUtils;

import com.autumn.platform.core.Defaults;
import com.autumn.platform.core.Defaults.Lookup;
import com.autumn.platform.core.Defaults.LookupFor;
import com.autumn.platform.core.accessor.IDataAccessorFactory;
import com.autumn.platform.core.accessor.impl.SpelDataAccessorFactory;
import com.autumn.platform.core.context.token.TokenService;
import com.autumn.platform.core.context.token.support.KeyBasedPersistenceTokenService;
import com.autumn.platform.core.context.token.support.SecureRandomFactoryBean;
import com.autumn.platform.core.spring.CacheManagerAspect;
import com.autumn.platform.core.spring.SpEL;
import com.autumn.platform.core.spring.SpringHolder;
import com.autumn.platform.core.support.param.base.IParamService;
import com.autumn.platform.core.support.param.base.impl.ParamService;

@Configuration
@EnableConfigurationProperties({CoreProperties.class})
public class CoreAutoConfiguration implements ApplicationContextAware, InitializingBean {

    private ApplicationContext applicationContext;

    private final CoreProperties properties;

    public CoreAutoConfiguration(CoreProperties properties) {
        super();
        this.properties = properties;
    }

    @Bean
    @ConditionalOnMissingBean(SecureRandomFactoryBean.class)
    public SecureRandomFactoryBean SecureRandomFactoryBean() {
        return new SecureRandomFactoryBean();
    }

    @Bean
    @ConditionalOnMissingBean(TokenService.class)
    @ConditionalOnBean(SecureRandom.class)
    public KeyBasedPersistenceTokenService tokenService(SecureRandom secureRandom) {
        KeyBasedPersistenceTokenService service = new KeyBasedPersistenceTokenService();
        service.setSecureRandom(secureRandom);
        service.setServerInteger(31);
        String secret = properties.getServerSecret();
        if (null == secret) {
            secret = "autumn";
        }
        service.setServerSecret(secret);
        return service;
    }

    @Bean
    @ConditionalOnMissingBean(IDataAccessorFactory.class)
    public IDataAccessorFactory dataAccessorFactory() {
        IDataAccessorFactory dataAccessorFactory = new SpelDataAccessorFactory();
        return dataAccessorFactory;
    }

    @Bean
    @ConditionalOnMissingBean(value = CacheManagerAspect.class)
    @ConditionalOnProperty(prefix = CoreProperties.PREFIX, name = "cache-name-prefix", matchIfMissing = false)
    public CacheManagerAspect CacheManagerAspect() {
        CacheManagerAspect cacheManagerAspect = new CacheManagerAspect();
        cacheManagerAspect.setCacheNamePrefix(properties.getCacheNamePrefix());
        return cacheManagerAspect;
    }

    @Bean
    @ConditionalOnMissingBean(value = IParamService.class)
    public IParamService paramService() {
        return new ParamService();
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        SpringHolder.setApplicationContext(applicationContext);
        SpEL.setApplicationContext(applicationContext);

        registerDefaultComponents();
    }

    private void registerDefaultComponents() {
        Map<String, Object> lookups = applicationContext.getBeansWithAnnotation(Lookup.class);
        if (null != lookups) {
            for (Object bean : lookups.values()) {
                Set<Class<?>> interfaces = ClassUtils.getAllInterfacesAsSet(bean);
                if (null != interfaces && !interfaces.isEmpty()) {
                    for (Class<?> cls : interfaces) {
                        if (cls.isAnnotationPresent(Lookup.class)) {
                            this.registerDefaultComponent(cls, bean);
                        }
                    }
                }
            }
        }

        Map<String, LookupFor> lookupFors = applicationContext.getBeansOfType(LookupFor.class);
        if (null != lookupFors) {
            Set<Class<?>> lookupClss = new HashSet<Class<?>>();
            for (LookupFor bean : lookupFors.values()) {
                Set<Class<?>> interfaces = bean.getClasses();
                if (null != interfaces && !interfaces.isEmpty()) {
                    lookupClss.addAll(interfaces);
                }
            }
            for (Class<?> cls : lookupClss) {
                if (cls.isInterface()) {
                    Object bean = this.getInterfaceBean(applicationContext, cls);
                    if (null != bean) {
                        this.registerDefaultComponent(cls, bean);
                    }
                }
            }
        }
    }

    @SuppressWarnings("unchecked")
    private <E> void registerDefaultComponent(Class<E> cls, Object bean) {
        Defaults.registerDefaultComponent(cls, (E) bean);
    }

    private Object getInterfaceBean(ApplicationContext applicationContext, Class<?> cls) {
        try {
            return applicationContext.getBean(cls);
        } catch (Exception e) {
            return null;
        }
    }
}
